#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
This module implements the basic event system.
"""

__version__ = '$Id$'

import sys
import os
import warnings

#-------------------------------------------------------------------------------
class AddTwiceWarning(UserWarning): 
    u"""
    Warning that a handler has been added twice.
    """
    pass

#-------------------------------------------------------------------------------
class RemoveWarning(UserWarning): 
    u"""
    Warning that a handler has been removed twice or was not present.
    """
    pass

#-------------------------------------------------------------------------------
class Event(object):
    u"""
    This is the event class. An event is works by storing a callback and 
    calling it when fired. For convenience you can use \'+=\' and \'-=\' to add
    and remove a handler function. The function can have any signature as long
    the arguments used to fire the event are the same. The handler should either
    return anything or one of the constansts Event.HANDLED or UNHANDLED
    Just calling the event
    using \'(\*args, \*\*kwargs)\' is the same as calling fire(\*args, \*\*kwargs).
    It is also save to remove or add handlers from within a handler of this 
    event.
    The call order depends on the order the handlers have been added:
    
    event = Event()
    event += on_handler1
    event += on_handler2
    event() # this will call the handlers in this order: handler2, handler1
    
    There is a convention about variable names:
    
        - an event should begin with \'event\_\' (e.g. event\_click = Event() )
        - a handler for a certain event should be \'on\_\' (e.g. on\_click() )
    
    Also make sure, that the arguments for a certain event is described.
    
    Sometimes it is usefull to get the sender (for unregistering on a event as
    an example). This can be easily be done, just pass a reference in the event
    argument: 
    
    .. python::
        event_bla = Event()
        event_bla(self, args) # handler signature: on_bla(sender, args)
    
    This way the sender can be used in the handler, but should not be stored.
    
    It is possible to stop further processing. It is done by returning 
    HANDLED from a handler:
    
    event = Event()
    event += on_handler1
    event += on_handler2 # returns HANDLED, stops processing
    event() # this will call the handlers in this order: handler2
    
    """

    HANDLED = property(lambda self: True, doc=u"""read-only, prevents further 
processing if returned from a handler""")
    UNHANDLED = property(lambda self: False, doc=u"""read-only, opposit \
of HANDLED""")
    
    def __init__(self, name = None, *args, **kwargs):
        u"""
        You can give this event a name with the name attribute. It is useful 
        for debugging.
        """
        super(Event, self).__init__(*args, **kwargs)
        if name is None:
            name = self
        self.name = name
        self._callbacks = []
        self._to_add = []
        self._to_remove = []
        self._changed = True

    def add(self, method):
        u"""
        Add a callback to the event, which will be called when fired. If
        __debug__ is true, this will generate a warning if you try to add the
        same callback twice. For convenience you also can use '+=' on the 
        event instance to add a handler.
        """
        if method not in self._callbacks and method not in self._to_add:
            self._to_add.append(method)
            self._changed = True
        else:
            if __debug__:
                msg = u"Tryed to add %s twice to %s, not added" % (method, (self.name if hasattr(self, 'name') else self) )
                warnings.warn( AddTwiceWarning(msg))
        return self

    def remove(self, method):
        u"""
        Removes the given method from this event. If __debug__ is active, 
        then a warning is generated if you try to remove it twice or remove it
        event if it isn't present. For convenience you can use '-=' on the 
        event instance to remove a handler.
        """
        if method not in self._to_remove:
            if method in self._to_add:
                self._to_add.remove(method)
            elif method in self._callbacks:
                self._to_remove.append(method)
                self._changed = True
            else:
                if __debug__:
                    msg = u"Tryed to remove %s which is not in event %s" % (method, (self.name if hasattr(self, 'name') else self) )
                    warnings.warn( RemoveWarning(msg) )
        else:
            if __debug__:
                msg = u"Tryed to remove %s twice from %s" % (method, (self.name if hasattr(self, 'name') else self) )
                warnings.warn( RemoveWarning(msg) )
        return self

    def fire(self, *args, **kwargs):
        u"""
        This fires the event passing the given arguments. For convenience 
        an Event instance is callable e.g.:
        .. python::
        
            event_new = Event()
            # fire the event
            event_new(needed_args, needed_kwargs)
        
        
        """
        assert sys.stdout.write(u"%s    firing %s with args %s %s" % (os.linesep, self.name, args, kwargs)) or True
        if self._changed:
            self._sync()
        for cb in self._callbacks:
            if cb(*args, **kwargs):
                assert sys.stdout.write(u"%s    listener %s returned handled" % (os.linesep, cb)) or True
                return self.HANDLED

    def clear(self):
        u"""
        Removes all handlers from the event.
        """
        self._sync()
        self._callbacks[:] = []

    def __len__(self):
        u"""
        Returns the number of handlers. Schould not be called from within a
        handler of this event.
        """
        self._sync()
        return len(self._callbacks)

    def _sync(self):
        u"""
        For internal use only. It transfers the add and remove requests to the
        actual list. Should not be called from within a handler of this event.
        """
        while self._to_add:
            self._callbacks.insert(0, self._to_add.pop(0))
        while self._to_remove:
            self._callbacks.remove(self._to_remove.pop())
        self._changed = False

    __iadd__ = add
    __isub__ = remove
    __call__ = fire

#-------------------------------------------------------------------------------

class ReversedEvent(Event):
    u"""
    This event works the same as Event(), but the call order is different 
    (they will be called the same order that they have been added):

    .. python::
    
        event = Event()
        event += on_handler1
        event += on_handler2
        event() # this will call the handlers in this order: handler1, handler2

    This can be usefull for a 'on_draw' event.
    """

    def __init__(self, *args, **kwargs):
        u"""
        Init, same like Event.
        """
        super(ReversedEvent, self).__init__(*args, **kwargs)

    def _sync(self):
        u"""
        For internal use only. It transfers the add and remove requests to the
        actual list. Should not be called from within a handler of this event.
        """
        while self._to_add:
            self._callbacks.append(self._to_add.pop(0))
        while self._to_remove:
            self._callbacks.remove(self._to_remove.pop())
        self._changed = False

#-------------------------------------------------------------------------------

#TODO: add WeakrefEvent


